package com.demo.common.ratelimit.aspect;

import com.demo.common.ratelimit.annotation.ApiIdempotent;
import com.demo.common.ratelimit.exception.RateLimitException;
import com.demo.common.ratelimit.utils.*;
import com.demo.common.redis.component.RedisKeys;
import com.demo.common.redis.component.StringRedisUtil;
import com.demo.common.redis.constant.RedisLuaConstant;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Order(1)
@Slf4j
@Aspect
public class ApiIdempotentAspect {

    @Resource
    public StringRedisUtil stringRedisUtil;

    @Pointcut("@annotation(com.demo.common.ratelimit.annotation.ApiIdempotent)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = HttpServletUtil.getRequest();
        if (request == null) {
            return joinPoint.proceed();
        }
        // 获取 使用注解的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        ApiIdempotent apiIdempotent = method.getDeclaredAnnotation(ApiIdempotent.class);
        //获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        //获取请求的方法名
        String methodName = joinPoint.getSignature().getName();

        String username = HttpUserInfoUtil.getSessionUser(request);
        if (!StringUtils.hasText(username)) {
            username = IpAddressUtil.getIpAddress(request);
            username += MD5Util.getMD5Str(UserAgent.parseUserAgentString(request.getHeader("user-agent")).toString());
        }
        String extraValue = null;
        try {
            if (StringUtils.hasText(apiIdempotent.extra())) {
                extraValue = SpelUtil.generateKeyBySpEL(apiIdempotent.extra(), joinPoint, null);
            }
        } catch (Exception e) {
            log.error("幂等性EL表达式解析异常",e);
            throw new RateLimitException("幂等性EL表达式解析异常");
        }
        String lockKey = RedisKeys.getRedisApiIdempotentKey(username, className, methodName,extraValue);
        String randomKey = RandomUtil.getUUID();

        try {
            boolean setIfAbsent = stringRedisUtil.setIfAbsent(lockKey, randomKey, apiIdempotent.expire(), apiIdempotent.timeUnit());
            if (setIfAbsent) {
                // 放行
                return joinPoint.proceed();
            }
            throw new RateLimitException("请勿重复提交");
        }finally {
            if (apiIdempotent.release()) {
                stringRedisUtil.execute(RedisLuaConstant.RELEASE_LOCK, lockKey, randomKey);
            }
        }

    }

}
